package org.glickr

import org.codehaus.groovy.grails.validation.routines.UrlValidator
import groovy.util.slurpersupport.GPathResult

class FlickrService {

    def grailsApplication

    String logMethod
    String logUri

    private final static flickrApiEndpoint       = "http://api.flickr.com/services/rest?method=###API_METHOD###&###API_PARAMS###"
    private final static flickrApiSecureEndpoint = "https://secure.flickr.com/services/rest?method=###API_METHOD###&###API_PARAMS###"
    private final static flickrApiFormat         = "REST"

    //
    //  actual call to the Flickr API
    //
    private def doApiCall(String apiMethod, Map mergeParams = [:]) throws FlickrServiceException {
        // configuration
        String apiKey    = grailsApplication.config.grails.plugins.glickr.apiKey    ?: ""
        String apiSecret = grailsApplication.config.grails.plugins.glickr.apiSecret ?: ""
        Map    apiParams = [api_key: apiKey, format: flickrApiFormat.toLowerCase()]

        if (!apiKey)    { throw new FlickrServiceApiException(message:"Configuration setting 'grails.plugins.glickr.apiKey' missing for FlickrService") }
        if (!apiSecret) { throw new FlickrServiceApiException(message:"Configuration setting 'grails.plugins.glickr.apiSecret' missing for FlickrService") }

        // merge all params and convert to URI string
        String paramsString = ((apiParams + mergeParams)?.collect() {
            it.value ? it.key + '=' + it.value : ''
        })?.join('&')

        // fill in static URI for API endpoint
        logUri = flickrApiEndpoint.replaceAll('###API_METHOD###',apiMethod.toLowerCase()).replaceAll('###API_PARAMS###',paramsString).toURI().toString()

        // validate the constucted url
        UrlValidator urlValidator = new UrlValidator();
        if (!urlValidator.isValid(logUri)) {
            throw new FlickrServicePluginException(
                method:  logMethod,
                uri:     logUri,
                message: 'Invalid URL while posting to remote server'
            )
        }

        // get response
        // println logUri
        GPathResult response
        try {
            response = new XmlSlurper().parse(logUri)
        } catch (Exception ex) {
            throw new FlickrServicePluginException(
                method:  logMethod,
                uri:     logUri,
                message: ex.toString()
            )
        }

        // if not a valid response
        if (!response || !response.name().toString().equalsIgnoreCase('rsp')) {
            throw new FlickrServicePluginException(
                method:  logMethod,
                uri:     logUri,
                message: 'Invalid or no response from remote server'
            )
        }
        return response
    }

    //
    //  abstracting the whole call handling with closures implemented in individual classes and connected below
    //
    private def apiCall(def apiImplementation, def apiModel = {} ) {
        def validator = apiImplementation.validatorClosure
        def params    = apiImplementation.paramsClosure
        def processor = apiImplementation.processorClosure
        def errors    = apiImplementation.errorsClosure

        // preserve the calling service method for logging etc
        StackTraceElement[] stackTraceElements = Thread.currentThread().getStackTrace().findAll{it.getClassName().equals('org.glickr.FlickrService')}
        logMethod = stackTraceElements.last()?.getMethodName()

        def validatorResult = validator(apiModel())

        if (validatorResult.validated) {
            GPathResult response = doApiCall(apiImplementation.apiMethod, params(apiModel()))
            if (!response.@stat.toString().equalsIgnoreCase('ok')) {
                // non-OK response, interpret Flickr API error code and translate to Groovy Exception
                FlickrServiceException ex = errors(response)

                if (!ex) {  // a non-OK response that is not really exceptions
                    return null
                } else {
                    ex.method = logMethod
                    ex.uri    = logUri
                    ex.api    = apiImplementation.apiMethod
                    throw ex
                }
            } else {
                // OK response, process into Groovy types
                return processor(response,apiModel())
            }
        } else {
            // call did not pass validation, raise a syntax error
            throw new FlickrServiceSyntaxException(
                method:  logMethod,
                uri:     logUri,
                message: validatorResult.message
            )
        }
    }

    // ---------------------------------------------------------------------------
    //
    // connecting the individual api methods with the individual classes that implement the interface
    //
    // ---------------------------------------------------------------------------

    //
    // activity
    //

    //
    // auth
    //

    //
    // blogs
    //

    //
    // collections
    //

    //
    // commons
    //

    //
    // contacts
    //

    //
    // favorites
    //

    //
    // galleries
    //

    //
    // groups
    //

    //
    // groups.discuss
    //

    //
    // groups.pools
    //

    //
    // interestingness
    //

    //
    // machinetags
    //

    //
    // people
    //
    FlickrPeople getPeopleById(String nsid) {
        FlickrPeople people = apiCall(new org.glickr.api.people.peopleGetInfo(), {new FlickrPeople(nsid:nsid)}) as FlickrPeople
        return people
    }
    FlickrPeople getPeopleByUsername(String username) {
        FlickrPeople people = apiCall(new org.glickr.api.people.peopleFindByUsername(), {new FlickrPeople(username:username)}) as FlickrPeople
        if (people) {
            people = apiCall(new org.glickr.api.people.peopleGetInfo(), {people}) as FlickrPeople
        } else { return people }
    }
    FlickrPeople getPeopleByEmail(String email) {
        FlickrPeople people = apiCall(new org.glickr.api.people.peopleFindByEmail(), {new FlickrPeople(email:email)}) as FlickrPeople
        if (people) {
            people = apiCall(new org.glickr.api.people.peopleGetInfo(), {people}) as FlickrPeople
        } else { return people }
    }

    FlickrPhotoSet getPeoplePublicPhotos(FlickrPeople people) {
        return apiCall(new org.glickr.api.people.peopleGetPublicPhotos(), {people}) as FlickrPhotoSet
    }
    FlickrPhotoSet getPeoplePublicPhotosById(String nsid) {
        return apiCall(new org.glickr.api.people.peopleGetPublicPhotos(), {new FlickrPeople(nsid:nsid)}) as FlickrPhotoSet
    }
    FlickrPhotoSet getPeoplePublicPhotosByUsername(String username) {
        FlickrPeople people = apiCall(new org.glickr.api.people.peopleFindByUsername(), {new FlickrPeople(username:username)}) as FlickrPeople
        if (people) {
            return apiCall(new org.glickr.api.people.peopleGetPublicPhotos(), {people}) as FlickrPhotoSet
        } else { return null }
    }
    FlickrPhotoSet getPeoplePublicPhotosByEmail(String email) {
        FlickrPeople people = apiCall(new org.glickr.api.people.peopleFindByEmail(), {new FlickrPeople(email:email)}) as FlickrPeople
        if (people) {
            return apiCall(new org.glickr.api.people.peopleGetPublicPhotos(), {people}) as FlickrPhotoSet
        } else { return null }
    }

//    String peopleEmailToId(String email) { // todo implement methods like these ? }

    //
    // photos
    //
    FlickrPhoto getPhotoById(Long id) {
        return apiCall(new org.glickr.api.photos.photosGetInfo(), {new FlickrPhoto(id:id)}) as FlickrPhoto
    }
    FlickrPhoto getPhotoDetails(FlickrPhoto photo) {
        photo = apiCall(new org.glickr.api.photos.photosGetInfo(),  {photo}) as FlickrPhoto
        photo = apiCall(new org.glickr.api.photos.photosGetSizes(), {photo}) as FlickrPhoto
        photo = apiCall(new org.glickr.api.photos.photosGetExif(),  {photo}) as FlickrPhoto
        return photo
    }
    FlickrPhotoSet findAll(def params) {
        return apiCall(new org.glickr.api.photos.photosSearch(), {params}) as FlickrPhotoSet
    }

//    FlickrPhoto apiPhotosGetInfo(FlickrPhoto photo)  { return apiCall(new org.glickr.api.photos.photosGetInfo(),  { photo })  as FlickrPhoto }
//    FlickrPhoto apiPhotosGetSizes(FlickrPhoto photo) { return apiCall(new org.glickr.api.photos.photosGetSizes(), { photo })  as FlickrPhoto }
//    FlickrPhoto apiPhotosGetExif(FlickrPhoto photo)  { return apiCall(new org.glickr.api.photos.photosGetExif(),  { photo })  as FlickrPhoto }


    //
    // photos.comments
    //

    //
    // photos.geo
    //
    FlickrPhoto geoGetLocation(Long id) {
        return apiCall(new org.glickr.api.photos.geo.geoGetLocation(), {new FlickrPhoto(id:id)}) as FlickrPhoto
    }

    //
    // photos.licenses
    //

    //
    // photos.notes
    //

    //
    // photos.suggestions
    //

    //
    // photos.transform
    //

    //
    // photos.upload
    //

    //
    // photosets
    //

    //
    // photosets.comments
    //

    //
    // places
    //
    ArrayList<FlickrPlaceType> placeTypes()       { return apiCall(new org.glickr.api.places.placesGetPlaceTypes(), {new Object()}) as ArrayList<FlickrPlaceType> }
    FlickrPlace placesGetInfo(FlickrPlace place)  { return apiCall(new org.glickr.api.places.placesGetInfo(),       {[place:place, placeTypes:placeTypes()]}) as FlickrPlace }

    //
    // prefs
    //

    //
    // push
    //

    //
    // reflection
    //

    //
    // stats
    //

    //
    // tags
    //

    //
    // test
    //

    //
    // urls
    //
}
